{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基于经典网络架构训练图像分类模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.整体流程\n",
    "1. 数据预处理部分：\n",
    "    1. 数据增强：torchvision中transforms模块自带功能，比较实用\n",
    "    2. 数据预处理：torchvision中transforms也帮我们实现好了，直接调用即可\n",
    "    3. DataLoader模块直接读取batch数据\n",
    "\n",
    "2. 网络模块设置：\n",
    "    1. 加载预训练模型，torchvision中有很多经典网络架构，调用起来十分方便，并且可以用人家训练好的权重参数来继续训练，也就是所谓的迁移学习\n",
    "    2. 需要注意的是别人训练好的任务跟咱们的可不是完全一样，需要把最后的head层改一改，一般也就是最后的全连接层，改成咱们自己的任务\n",
    "    3. 训练时可以全部重头训练，也可以只训练最后咱们任务的层，因为前几层都是做特征提取的，本质任务目标是一致的\n",
    "\n",
    "3. 网络模型保存与测试\n",
    "    1. 模型保存的时候可以带有选择性，例如在验证集中如果当前效果好则保存\n",
    "    2. 读取模型进行实际测试\n",
    "\n",
    "![title](1.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 530
    },
    "colab_type": "code",
    "id": "Qqk2lCKzYf-y",
    "outputId": "2354909a-d734-4dd8-e887-eb5373d9127e",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:24:37.276389200Z",
     "start_time": "2024-04-10T14:24:37.266938600Z"
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import torch\n",
    "from torch import nn\n",
    "import torch.optim as optim\n",
    "import torch.utils.data\n",
    "import torchvision\n",
    "#pip install torchvision\n",
    "from torchvision import transforms, models, datasets\n",
    "#https://pytorch.org/docs/stable/torchvision/index.html\n",
    "import imageio\n",
    "import time\n",
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "import random\n",
    "import sys\n",
    "import copy\n",
    "import json\n",
    "from PIL import Image"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Wa2qCaSOd9_J"
   },
   "source": [
    "# 2.具体流程\n",
    "## 2.1 数据读取与预处理操作"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "collapsed": true,
    "id": "S2lHfvKMd9_J",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:24:40.498064900Z",
     "start_time": "2024-04-10T14:24:40.478730100Z"
    }
   },
   "outputs": [],
   "source": [
    "obj_dir = os.path.abspath(os.getcwd())  # 当前脚本目录\n",
    "data_dir = os.path.join(obj_dir, \"flower_data\")  # 训练集\n",
    "train_dir = os.path.join(obj_dir, \"train\")  # 训练集图片目录\n",
    "valid_dir = os.path.join(obj_dir, \"valid\")  # 验证集图片目录"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1.1 制作好数据源：\n",
    "- data_transforms中指定了所有图像预处理操作\n",
    "- ImageFolder假设所有的文件按文件夹保存好，每个文件夹下面存贮同一类别的图片，文件夹的名字为分类的名字"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "5y6IxsmtQ0hR",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:24:43.751025200Z",
     "start_time": "2024-04-10T14:24:43.743793600Z"
    }
   },
   "outputs": [],
   "source": [
    "data_transforms = {\n",
    "    'train': \n",
    "        transforms.Compose([  # 组合操作，按照顺序去做下面这些事\n",
    "        transforms.Resize([96, 96]),    # 对大小不一的图片统一大小，小的数值迭代快，精度损失大；大的数值迭代慢，损失精度小。\n",
    "        transforms.RandomRotation(45),  # 数据增强操作：随机旋转，-45到45度之间随机选(上限45)\n",
    "        transforms.CenterCrop(64),      # 从中心开始裁剪 64*64的区域，最后得到的图像是64*64的大小\n",
    "        transforms.RandomHorizontalFlip(p=0.5),     # 随机水平翻转，定义一个概率\n",
    "        transforms.RandomVerticalFlip(p=0.5),       # 随机垂直翻转，定义一个概率\n",
    "        transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),  # 参数1为亮度，参数2为对比度，参数3为饱和度，参数4为色相\n",
    "        transforms.RandomGrayscale(p=0.025),    # 概率转换成灰度率，3通道就是R=G=B\n",
    "        transforms.ToTensor(),                  # 转为tensor类型\n",
    "        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 标准化操作：(均值, 标准差)  [R, G, B]\n",
    "    ]),\n",
    "    'valid': \n",
    "        transforms.Compose([\n",
    "        transforms.Resize([64, 64]),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 需要和训练数据相同\n",
    "    ]),\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "ebneIHSWP74J",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:24:46.016375200Z",
     "start_time": "2024-04-10T14:24:45.929867100Z"
    }
   },
   "outputs": [],
   "source": [
    "# # 每次训练网络时，输入数据集大小，小批次数据的数量\n",
    "batch_size = 128\n",
    "# 构建数据集\n",
    "image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'valid']}\n",
    "# 构建数据迭代器\n",
    "dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}\n",
    "# 查看训练集及验证集的大小\n",
    "dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}\n",
    "# 查看训练集的类别\n",
    "class_names = image_datasets['train'].classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "outputs": [
    {
     "data": {
      "text/plain": "{'train': Dataset ImageFolder\n     Number of datapoints: 6552\n     Root location: F:\\02_Python\\01_Code\\12_GuPaoPytorch\\03深度学习核心框架Pytorch\\03_图像识别模型与训练策略（重点）\\flower_data\\train\n     StandardTransform\n Transform: Compose(\n                Resize(size=[96, 96], interpolation=bilinear, max_size=None, antialias=True)\n                RandomRotation(degrees=[-45.0, 45.0], interpolation=nearest, expand=False, fill=0)\n                CenterCrop(size=(64, 64))\n                RandomHorizontalFlip(p=0.5)\n                RandomVerticalFlip(p=0.5)\n                ColorJitter(brightness=(0.8, 1.2), contrast=(0.9, 1.1), saturation=(0.9, 1.1), hue=(-0.1, 0.1))\n                RandomGrayscale(p=0.025)\n                ToTensor()\n                Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ),\n 'valid': Dataset ImageFolder\n     Number of datapoints: 818\n     Root location: F:\\02_Python\\01_Code\\12_GuPaoPytorch\\03深度学习核心框架Pytorch\\03_图像识别模型与训练策略（重点）\\flower_data\\valid\n     StandardTransform\n Transform: Compose(\n                Resize(size=[64, 64], interpolation=bilinear, max_size=None, antialias=True)\n                ToTensor()\n                Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            )}"
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看数据集\n",
    "image_datasets"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-04-10T14:24:48.450213400Z",
     "start_time": "2024-04-10T14:24:48.440227800Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-10T14:24:51.354880Z",
     "start_time": "2024-04-10T14:24:51.346109500Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "{'train': <torch.utils.data.dataloader.DataLoader at 0x219280bb690>,\n 'valid': <torch.utils.data.dataloader.DataLoader at 0x219280baf50>}"
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看数据迭代器\n",
    "dataloaders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-10T14:24:53.308804900Z",
     "start_time": "2024-04-10T14:24:53.292865Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "{'train': 6552, 'valid': 818}"
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看训练集及验证集的大小\n",
    "dataset_sizes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8JvBFT6qd9_M"
   },
   "source": [
    "### 读取标签对应的实际名字"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "collapsed": true,
    "id": "YlHjfyDRd9_N",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:24:57.542129900Z",
     "start_time": "2024-04-10T14:24:57.539912700Z"
    }
   },
   "outputs": [],
   "source": [
    "with open('cat_to_name.json', 'r') as f:\n",
    "    cat_to_name = json.load(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1751
    },
    "colab_type": "code",
    "id": "SiPBCbEpqX8P",
    "outputId": "0bbdec1b-65bc-4999-9e43-4b448c979cb8",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:25:00.346059700Z",
     "start_time": "2024-04-10T14:25:00.333736900Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "{'21': 'fire lily',\n '3': 'canterbury bells',\n '45': 'bolero deep blue',\n '1': 'pink primrose',\n '34': 'mexican aster',\n '27': 'prince of wales feathers',\n '7': 'moon orchid',\n '16': 'globe-flower',\n '25': 'grape hyacinth',\n '26': 'corn poppy',\n '79': 'toad lily',\n '39': 'siam tulip',\n '24': 'red ginger',\n '67': 'spring crocus',\n '35': 'alpine sea holly',\n '32': 'garden phlox',\n '10': 'globe thistle',\n '6': 'tiger lily',\n '93': 'ball moss',\n '33': 'love in the mist',\n '9': 'monkshood',\n '102': 'blackberry lily',\n '14': 'spear thistle',\n '19': 'balloon flower',\n '100': 'blanket flower',\n '13': 'king protea',\n '49': 'oxeye daisy',\n '15': 'yellow iris',\n '61': 'cautleya spicata',\n '31': 'carnation',\n '64': 'silverbush',\n '68': 'bearded iris',\n '63': 'black-eyed susan',\n '69': 'windflower',\n '62': 'japanese anemone',\n '20': 'giant white arum lily',\n '38': 'great masterwort',\n '4': 'sweet pea',\n '86': 'tree mallow',\n '101': 'trumpet creeper',\n '42': 'daffodil',\n '22': 'pincushion flower',\n '2': 'hard-leaved pocket orchid',\n '54': 'sunflower',\n '66': 'osteospermum',\n '70': 'tree poppy',\n '85': 'desert-rose',\n '99': 'bromelia',\n '87': 'magnolia',\n '5': 'english marigold',\n '92': 'bee balm',\n '28': 'stemless gentian',\n '97': 'mallow',\n '57': 'gaura',\n '40': 'lenten rose',\n '47': 'marigold',\n '59': 'orange dahlia',\n '48': 'buttercup',\n '55': 'pelargonium',\n '36': 'ruby-lipped cattleya',\n '91': 'hippeastrum',\n '29': 'artichoke',\n '71': 'gazania',\n '90': 'canna lily',\n '18': 'peruvian lily',\n '98': 'mexican petunia',\n '8': 'bird of paradise',\n '30': 'sweet william',\n '17': 'purple coneflower',\n '52': 'wild pansy',\n '84': 'columbine',\n '12': \"colt's foot\",\n '11': 'snapdragon',\n '96': 'camellia',\n '23': 'fritillary',\n '50': 'common dandelion',\n '44': 'poinsettia',\n '53': 'primula',\n '72': 'azalea',\n '65': 'californian poppy',\n '80': 'anthurium',\n '76': 'morning glory',\n '37': 'cape flower',\n '56': 'bishop of llandaff',\n '60': 'pink-yellow dahlia',\n '82': 'clematis',\n '58': 'geranium',\n '75': 'thorn apple',\n '41': 'barbeton daisy',\n '95': 'bougainvillea',\n '43': 'sword lily',\n '83': 'hibiscus',\n '78': 'lotus lotus',\n '88': 'cyclamen',\n '94': 'foxglove',\n '81': 'frangipani',\n '74': 'rose',\n '89': 'watercress',\n '73': 'water lily',\n '46': 'wallflower',\n '77': 'passion flower',\n '51': 'petunia'}"
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cat_to_name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "wSBGNSA8RPfU"
   },
   "source": [
    "### 加载models中提供的模型，并且直接用训练的好权重当做初始化参数\n",
    "- 第一次执行需要下载，可能会比较慢，我会提供给大家一份下载好的，可以直接放到相应路径"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "cellView": "form",
    "colab": {},
    "colab_type": "code",
    "collapsed": true,
    "id": "OWVvodU_PfBC",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:25:07.646362200Z",
     "start_time": "2024-04-10T14:25:07.630916900Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CUDA is available!  Training on GPU ...\n"
     ]
    }
   ],
   "source": [
    "# 可选的比较多 ['resnet', 'alexnet', 'vgg', 'squeezenet', 'densenet', 'inception']\n",
    "model_name = 'resnet'\n",
    "# 是否用人家训练好的特征来做\n",
    "feature_extract = True # 都用人家特征，咱先不更新\n",
    "\n",
    "# 是否用GPU训练\n",
    "train_on_gpu = torch.cuda.is_available()\n",
    "if not train_on_gpu:\n",
    "    print('CUDA is not available.  Training on CPU ...')\n",
    "else:\n",
    "    print('CUDA is available!  Training on GPU ...')\n",
    "# 优先使用GPU进行计算\n",
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 模型参数要不要更新\n",
    "- 有时候用人家模型，就一直用了，更不更新咱们可以自己定"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "collapsed": true,
    "id": "tfjrGAg5W5dE",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:25:40.108713100Z",
     "start_time": "2024-04-10T14:25:40.023703300Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ResNet(\n",
      "  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n",
      "  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "  (relu): ReLU(inplace=True)\n",
      "  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)\n",
      "  (layer1): Sequential(\n",
      "    (0): BasicBlock(\n",
      "      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (relu): ReLU(inplace=True)\n",
      "      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    )\n",
      "    (1): BasicBlock(\n",
      "      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (relu): ReLU(inplace=True)\n",
      "      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    )\n",
      "  )\n",
      "  (layer2): Sequential(\n",
      "    (0): BasicBlock(\n",
      "      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
      "      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (relu): ReLU(inplace=True)\n",
      "      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (downsample): Sequential(\n",
      "        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
      "        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      )\n",
      "    )\n",
      "    (1): BasicBlock(\n",
      "      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (relu): ReLU(inplace=True)\n",
      "      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    )\n",
      "  )\n",
      "  (layer3): Sequential(\n",
      "    (0): BasicBlock(\n",
      "      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
      "      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (relu): ReLU(inplace=True)\n",
      "      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (downsample): Sequential(\n",
      "        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
      "        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      )\n",
      "    )\n",
      "    (1): BasicBlock(\n",
      "      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (relu): ReLU(inplace=True)\n",
      "      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    )\n",
      "  )\n",
      "  (layer4): Sequential(\n",
      "    (0): BasicBlock(\n",
      "      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
      "      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (relu): ReLU(inplace=True)\n",
      "      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (downsample): Sequential(\n",
      "        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
      "        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      )\n",
      "    )\n",
      "    (1): BasicBlock(\n",
      "      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "      (relu): ReLU(inplace=True)\n",
      "      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
      "      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    )\n",
      "  )\n",
      "  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n",
      "  (fc): Linear(in_features=512, out_features=1000, bias=True)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "def set_parameter_requires_grad(model, feature_extracting):\n",
    "    \"\"\"\n",
    "    设置模型参数是否需要梯度更新\n",
    "    Args:\n",
    "        model (torch model): 待设置的模型\n",
    "        feature_extracting (bool): 是否冻结模型的特征提取部分\n",
    "    \"\"\"\n",
    "    if feature_extracting:\n",
    "        for param in model.parameters():\n",
    "            param.requires_grad = False  # 如果feature_extracting为True，则冻结模型参数，不需要梯度更新\n",
    "# 18层的能快点，条件好点的也可以选152\n",
    "model_ft = models.resnet18()\n",
    "print(model_ft)  # 打印ResNet-18模型结构"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "此时的模型都是人家的模型，根据模型结构可以看出，该模型的输出层是1000分类的，而我们的需求是进行102分类，所以需要将模型的输出层改为我们自己的。\n",
    "### 把模型输出层改成自己的"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "collapsed": true,
    "id": "D85BPk2GXBbj",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:26:31.410315800Z",
     "start_time": "2024-04-10T14:26:31.401621400Z"
    }
   },
   "outputs": [],
   "source": [
    "def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):\n",
    "    \"\"\"\n",
    "    初始化模型\n",
    "    Args:\n",
    "        model_name (str): 模型的名称\n",
    "        num_classes (int): 分类的类别数\n",
    "        feature_extract (bool): 是否冻结模型的特征提取部分\n",
    "        use_pretrained (bool): 是否使用预训练模型，默认为True \n",
    "    Returns:\n",
    "        model_ft (torch model): 初始化后的模型\n",
    "        input_size (int): 输入的大小\n",
    "    \"\"\"\n",
    "    model_ft = models.resnet18(pretrained=use_pretrained)  # 使用ResNet-18作为基础模型  应该使用参数model_name\n",
    "    set_parameter_requires_grad(model_ft, feature_extract)  # 根据feature_extract决定是否冻结部分参数\n",
    "    \n",
    "    num_ftrs = model_ft.fc.in_features\n",
    "    model_ft.fc = nn.Linear(num_ftrs, num_classes) # 替换模型的全连接层，输出维度为num_classes，根据任务需求确定\n",
    "                            \n",
    "    input_size = 64 # 输入图片大小，根据实际情况设定\n",
    "\n",
    "    return model_ft, input_size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "o3PCsZshRgSG"
   },
   "source": [
    "### 设置哪些层需要训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 102
    },
    "colab_type": "code",
    "id": "Nu5_c1aNU3L9",
    "outputId": "46daeef6-d017-409f-a154-8ca530765f80",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:26:54.525866600Z",
     "start_time": "2024-04-10T14:26:54.394954600Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Params to learn:\n",
      "\t fc.weight\n",
      "\t fc.bias\n"
     ]
    }
   ],
   "source": [
    "# 获取我们的模型，是根据人家的预训练模型，根据我们的需求修改输出层之后得到 (模型名称, 输出层分类数, 是否冻结模型的特征提取部分, 是否使用预训练模型)\n",
    "model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)\n",
    "\n",
    "# 将模型移动到GPU或CPU上进行计算\n",
    "model_ft = model_ft.to(device)\n",
    "\n",
    "# 模型保存文件名\n",
    "filename='best.pt'\n",
    "\n",
    "# 是否训练所有层，确定哪些参数需要更新\n",
    "params_to_update = model_ft.parameters()  # 获取模型中所有的层名称及参数\n",
    "print(\"Params to learn:\")\n",
    "if feature_extract:\n",
    "    params_to_update = []  # 保存梯度\n",
    "    for name,param in model_ft.named_parameters():\n",
    "        if param.requires_grad == True:\n",
    "            params_to_update.append(param)\n",
    "            print(\"\\t\",name)  # 输出需要更新的参数名\n",
    "else:\n",
    "    for name,param in model_ft.named_parameters():\n",
    "        if param.requires_grad == True:\n",
    "            print(\"\\t\",name)  # 输出需要更新的参数名"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-10T14:28:50.519796100Z",
     "start_time": "2024-04-10T14:28:50.511592Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "ResNet(\n  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n  (relu): ReLU(inplace=True)\n  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)\n  (layer1): Sequential(\n    (0): BasicBlock(\n      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (relu): ReLU(inplace=True)\n      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n    )\n    (1): BasicBlock(\n      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (relu): ReLU(inplace=True)\n      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n    )\n  )\n  (layer2): Sequential(\n    (0): BasicBlock(\n      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (relu): ReLU(inplace=True)\n      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (downsample): Sequential(\n        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)\n        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      )\n    )\n    (1): BasicBlock(\n      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (relu): ReLU(inplace=True)\n      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n    )\n  )\n  (layer3): Sequential(\n    (0): BasicBlock(\n      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (relu): ReLU(inplace=True)\n      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (downsample): Sequential(\n        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)\n        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      )\n    )\n    (1): BasicBlock(\n      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (relu): ReLU(inplace=True)\n      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n    )\n  )\n  (layer4): Sequential(\n    (0): BasicBlock(\n      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (relu): ReLU(inplace=True)\n      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (downsample): Sequential(\n        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)\n        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      )\n    )\n    (1): BasicBlock(\n      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n      (relu): ReLU(inplace=True)\n      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n    )\n  )\n  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n  (fc): Linear(in_features=512, out_features=102, bias=True)\n)"
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model_ft"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "M0RxpAEhR7lL"
   },
   "source": [
    "此时输出层的分类个数就由1000变为我们的102了\n",
    "### 优化器设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "collapsed": true,
    "id": "baLfYCINRI_H",
    "ExecuteTime": {
     "end_time": "2024-04-10T14:57:02.629543700Z",
     "start_time": "2024-04-10T14:57:02.625036Z"
    }
   },
   "outputs": [],
   "source": [
    "# 优化器设置\n",
    "# 使用Adam优化器，用于更新模型中需要训练的参数。params_to_update是需要更新的参数列表，lr=1e-2表示学习率为0.01。\n",
    "optimizer_ft = optim.Adam(params_to_update, lr=1e-2)\n",
    "# 定义一个学习率调度器，采用StepLR策略。optimizer_ft是要调整学习率的优化器，step_size=10表示每隔10个epoch调整一次学习率，gamma=0.1表示将学习率衰减为原来的1/10。\n",
    "scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=10, gamma=0.1)\n",
    "# 定义交叉熵损失函数，通常用于多分类问题中。在深度学习模型的训练过程中，通过最小化交叉熵损失函数来优化模型参数，使模型能够更好地拟合训练数据。\n",
    "criterion = nn.CrossEntropyLoss()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "s-PpMk90ShRz"
   },
   "source": [
    "### 训练模块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "EzPXJbyZXuK0",
    "ExecuteTime": {
     "end_time": "2024-04-10T15:39:26.899837700Z",
     "start_time": "2024-04-10T15:39:26.887577900Z"
    }
   },
   "outputs": [],
   "source": [
    "def train_model(model, dataloaders, criterion, optimizer, num_epochs=25, filename='best.pt'):\n",
    "    \"\"\"\n",
    "    训练模型\n",
    "    :param model: 前面说的ResNET\n",
    "    :param dataloaders: 数据迭代器\n",
    "    :param criterion: 损失函数\n",
    "    :param optimizer: 优化器\n",
    "    :param num_epochs: 默认迭代次数\n",
    "    :param filename: 最佳模型文件名\n",
    "    :return: 训练后的模型以及训练过程中的验证集准确率历史、训练集准确率历史、验证集损失历史、训练集损失历史和学习率历史\n",
    "    \"\"\"\n",
    "    since = time.time()  # 记录训练开始的时间。\n",
    "    best_acc = 0  # 初始化最佳准确率为0，用于记录训练过程中的最佳准确率。\n",
    "    model.to(device)  # 将模型移动到指定的设备（CPU或GPU）上进行训练。\n",
    "    # 记录一些指标的历史信息  验证集准确率/训练集准确率、训练损失、验证损失\n",
    "    val_acc_history, train_acc_history, train_losses, valid_losses = [], [], [], []        \n",
    "    # 获取当前的学习率\n",
    "    LRs = [optimizer.param_groups[0]['lr']]\n",
    "    # 初始化最好的那次模型，后续会被更新\n",
    "    best_model_wts = copy.deepcopy(model.state_dict())\n",
    "    # 循环遍历每个epoch进行训练。\n",
    "    # 每个epoch都包含训练和验证，训练和验证都要进行前向传播，区别是训练阶段要多走一个反向传播和参数更新\n",
    "    for epoch in range(num_epochs):\n",
    "        print('Epoch {}/{}'.format(epoch, num_epochs - 1))\n",
    "        print('-' * 10)\n",
    "\n",
    "        # 针对训练和验证集分别进行操作\n",
    "        for phase in ['train', 'valid']:\n",
    "            if phase == 'train':\n",
    "                model.train()  # 训练\n",
    "            else:\n",
    "                model.eval()   # 验证\n",
    "\n",
    "            # 累积每个epoch的总损失和正确预测的数量\n",
    "            running_loss = 0.0\n",
    "            running_corrects = 0\n",
    "\n",
    "            # 遍历训练集和验证集，先遍历的是训练集\n",
    "            for inputs, labels in dataloaders[phase]:\n",
    "                # 移动到CPU或GPU上进行计算\n",
    "                inputs = inputs.to(device)  # 训练集\n",
    "                labels = labels.to(device)  # 验证集\n",
    "\n",
    "                # 梯度清零\n",
    "                optimizer.zero_grad()\n",
    "                # 只有训练的时候计算和更新梯度\n",
    "                outputs = model(inputs)  # 得到102个数字\n",
    "                loss = criterion(outputs, labels)  # 计算损失率\n",
    "                _, preds = torch.max(outputs, 1)\n",
    "                # 训练阶段更新权重\n",
    "                if phase == 'train':\n",
    "                    loss.backward()  # 反向传播\n",
    "                    optimizer.step()  # 参数的更新\n",
    "\n",
    "                # 计算当前迭代损失\n",
    "                running_loss += loss.item() * inputs.size(0)  # 0表示batch那个维度，计算当前batch的损失\n",
    "                running_corrects += torch.sum(preds == labels.data)  # 预测结果最大的和真实值是否一致，进行累加\n",
    "                \n",
    "            # 计算当前epoch平均损失及平均准确率\n",
    "            epoch_loss = running_loss / len(dataloaders[phase].dataset) # 平均损失\n",
    "            epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)  # 平均准确率\n",
    "            \n",
    "            time_elapsed = time.time() - since  # 一个epoch我训练了多少时间\n",
    "            print('Time elapsed {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))\n",
    "            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))  # 打印损失及准确率\n",
    "            \n",
    "\n",
    "            # 得到最好那次的模型\n",
    "            if phase == 'valid' and epoch_acc > best_acc:\n",
    "                best_acc = epoch_acc\n",
    "                best_model_wts = copy.deepcopy(model.state_dict())\n",
    "                state = {\n",
    "                  'state_dict': model.state_dict(),# 字典里key就是各层的名字，值就是训练好的权重\n",
    "                  'best_acc': best_acc,  # 准确率\n",
    "                  'optimizer' : optimizer.state_dict(),  # 优化器参数\n",
    "                }\n",
    "                torch.save(state, filename)  # 将最佳模型的状态信息保存到文件中\n",
    "            if phase == 'valid':\n",
    "                val_acc_history.append(epoch_acc)  # 保存验证集准确率\n",
    "                valid_losses.append(epoch_loss)  # 平均损失\n",
    "                #scheduler.step(epoch_loss)  # 调整学习率，这里使用的是StepLR策略\n",
    "            if phase == 'train':\n",
    "                train_acc_history.append(epoch_acc)  # 保存训练集准确率\n",
    "                train_losses.append(epoch_loss)  # 平均损失\n",
    "        # 遍历完一次epoch之后，保存当前学习率\n",
    "        print('Optimizer learning rate : {:.7f}'.format(optimizer.param_groups[0]['lr']))\n",
    "        LRs.append(optimizer.param_groups[0]['lr'])\n",
    "        scheduler.step()  # 使用定义好的衰减器对学习率进行衰减，并不是每次都会进行衰减，根据学习率调度器的step_size参数而定\n",
    "\n",
    "    time_elapsed = time.time() - since\n",
    "    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))  # 训练总时间\n",
    "    print('Best val Acc: {:4f}'.format(best_acc))  # 验证集中最高的准确率\n",
    "\n",
    "    # 训练完后用最好的一次当做模型最终的结果,等着一会测试\n",
    "    model.load_state_dict(best_model_wts)\n",
    "    # 返回训练后的模型以及训练过程中的验证集准确率历史、训练集准确率历史、验证集损失历史、训练集损失历史和学习率历史。\n",
    "    return model, val_acc_history, train_acc_history, valid_losses, train_losses, LRs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 开始训练！\n",
    "\n",
    "- 我们现在只训练了输出层"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "MXbS16p_j5U0",
    "ExecuteTime": {
     "end_time": "2024-04-10T15:53:20.594763400Z",
     "start_time": "2024-04-10T15:40:01.937467500Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0/19\n",
      "----------\n",
      "Time elapsed 2m 19s\n",
      "train Loss: 3.9078 Acc: 0.2518\n",
      "Time elapsed 2m 30s\n",
      "valid Loss: 3.5212 Acc: 0.2812\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 1/19\n",
      "----------\n",
      "Time elapsed 3m 1s\n",
      "train Loss: 2.7854 Acc: 0.3968\n",
      "Time elapsed 3m 5s\n",
      "valid Loss: 3.7134 Acc: 0.2848\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 2/19\n",
      "----------\n",
      "Time elapsed 3m 35s\n",
      "train Loss: 2.8272 Acc: 0.4176\n",
      "Time elapsed 3m 38s\n",
      "valid Loss: 3.8268 Acc: 0.2653\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 3/19\n",
      "----------\n",
      "Time elapsed 4m 9s\n",
      "train Loss: 2.7454 Acc: 0.4376\n",
      "Time elapsed 4m 12s\n",
      "valid Loss: 3.9507 Acc: 0.2848\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 4/19\n",
      "----------\n",
      "Time elapsed 4m 42s\n",
      "train Loss: 2.7067 Acc: 0.4383\n",
      "Time elapsed 4m 45s\n",
      "valid Loss: 4.0591 Acc: 0.2812\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 5/19\n",
      "----------\n",
      "Time elapsed 5m 15s\n",
      "train Loss: 2.7401 Acc: 0.4568\n",
      "Time elapsed 5m 18s\n",
      "valid Loss: 4.0525 Acc: 0.3166\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 6/19\n",
      "----------\n",
      "Time elapsed 5m 49s\n",
      "train Loss: 2.8160 Acc: 0.4609\n",
      "Time elapsed 5m 52s\n",
      "valid Loss: 4.1951 Acc: 0.3105\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 7/19\n",
      "----------\n",
      "Time elapsed 6m 23s\n",
      "train Loss: 2.7980 Acc: 0.4632\n",
      "Time elapsed 6m 26s\n",
      "valid Loss: 4.3702 Acc: 0.2836\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 8/19\n",
      "----------\n",
      "Time elapsed 6m 56s\n",
      "train Loss: 2.7901 Acc: 0.4567\n",
      "Time elapsed 6m 59s\n",
      "valid Loss: 4.2903 Acc: 0.3130\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 9/19\n",
      "----------\n",
      "Time elapsed 7m 30s\n",
      "train Loss: 2.8871 Acc: 0.4574\n",
      "Time elapsed 7m 33s\n",
      "valid Loss: 4.3718 Acc: 0.3068\n",
      "Optimizer learning rate : 0.0100000\n",
      "Epoch 10/19\n",
      "----------\n",
      "Time elapsed 8m 3s\n",
      "train Loss: 2.3812 Acc: 0.5133\n",
      "Time elapsed 8m 6s\n",
      "valid Loss: 3.8138 Acc: 0.3411\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 11/19\n",
      "----------\n",
      "Time elapsed 8m 38s\n",
      "train Loss: 2.0785 Acc: 0.5469\n",
      "Time elapsed 8m 41s\n",
      "valid Loss: 3.6808 Acc: 0.3545\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 12/19\n",
      "----------\n",
      "Time elapsed 9m 13s\n",
      "train Loss: 2.0865 Acc: 0.5389\n",
      "Time elapsed 9m 16s\n",
      "valid Loss: 3.6341 Acc: 0.3447\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 13/19\n",
      "----------\n",
      "Time elapsed 9m 47s\n",
      "train Loss: 1.9974 Acc: 0.5557\n",
      "Time elapsed 9m 51s\n",
      "valid Loss: 3.6031 Acc: 0.3533\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 14/19\n",
      "----------\n",
      "Time elapsed 10m 23s\n",
      "train Loss: 1.9172 Acc: 0.5646\n",
      "Time elapsed 10m 26s\n",
      "valid Loss: 3.5635 Acc: 0.3655\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 15/19\n",
      "----------\n",
      "Time elapsed 10m 58s\n",
      "train Loss: 1.8935 Acc: 0.5639\n",
      "Time elapsed 11m 2s\n",
      "valid Loss: 3.5069 Acc: 0.3509\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 16/19\n",
      "----------\n",
      "Time elapsed 11m 33s\n",
      "train Loss: 1.8986 Acc: 0.5646\n",
      "Time elapsed 11m 36s\n",
      "valid Loss: 3.4084 Acc: 0.3680\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 17/19\n",
      "----------\n",
      "Time elapsed 12m 7s\n",
      "train Loss: 1.9091 Acc: 0.5699\n",
      "Time elapsed 12m 10s\n",
      "valid Loss: 3.4165 Acc: 0.3680\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 18/19\n",
      "----------\n",
      "Time elapsed 12m 41s\n",
      "train Loss: 1.8160 Acc: 0.5789\n",
      "Time elapsed 12m 45s\n",
      "valid Loss: 3.3902 Acc: 0.3704\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 19/19\n",
      "----------\n",
      "Time elapsed 13m 15s\n",
      "train Loss: 1.7959 Acc: 0.5749\n",
      "Time elapsed 13m 19s\n",
      "valid Loss: 3.3488 Acc: 0.3545\n",
      "Optimizer learning rate : 0.0010000\n",
      "Training complete in 13m 19s\n",
      "Best val Acc: 0.370416\n"
     ]
    }
   ],
   "source": [
    "model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs  = train_model(model_ft, dataloaders, criterion, optimizer_ft, num_epochs=20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "7rjvtqx0TGkb"
   },
   "source": [
    "### 再继续训练所有层\n",
    "之前的训练，我们将模型除全连接层外的所有层全部冻结了，只训练了全连接层。现在全连接层训练完毕了，得到了一个相对不错的参数。\n",
    "那么我们现在将之前冻结的卷积层全部解冻，再进行训练，这样就能够训练所有层了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "collapsed": true,
    "id": "cSGcACfmrIC6",
    "ExecuteTime": {
     "end_time": "2024-04-10T15:57:36.166983700Z",
     "start_time": "2024-04-10T15:57:36.163278200Z"
    }
   },
   "outputs": [],
   "source": [
    "# 遍历模型的所有参数，将requires_grad设置为True，在接下来的训练中就会执行所有的权重参数进行训练了\n",
    "for param in model_ft.parameters():\n",
    "    param.requires_grad = True\n",
    "\n",
    "# 再继续训练所有的参数，学习率调小一点\n",
    "optimizer = optim.Adam(model_ft.parameters(), lr=1e-3)  # 训练模型的所有参数\n",
    "scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)  # 学习率调度器\n",
    "\n",
    "# 损失函数\n",
    "criterion = nn.CrossEntropyLoss()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "LFiiMNWaHXfw",
    "ExecuteTime": {
     "end_time": "2024-04-10T15:57:39.079564600Z",
     "start_time": "2024-04-10T15:57:39.001178300Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<All keys matched successfully>"
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 加载之前训练好的权重参数\n",
    "checkpoint = torch.load(filename)\n",
    "best_acc = checkpoint['best_acc']\n",
    "model_ft.load_state_dict(checkpoint['state_dict'])  # 使用之前的所有权重参数替换当前模型的权重参数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "_JCiyD-ytYRu",
    "ExecuteTime": {
     "end_time": "2024-04-10T16:03:51.253386900Z",
     "start_time": "2024-04-10T15:58:05.560848400Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0/9\n",
      "----------\n",
      "Time elapsed 0m 31s\n",
      "train Loss: 2.8933 Acc: 0.4119\n",
      "Time elapsed 0m 34s\n",
      "valid Loss: 2.4064 Acc: 0.4218\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 1/9\n",
      "----------\n",
      "Time elapsed 1m 6s\n",
      "train Loss: 1.4588 Acc: 0.6134\n",
      "Time elapsed 1m 9s\n",
      "valid Loss: 2.1608 Acc: 0.5024\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 2/9\n",
      "----------\n",
      "Time elapsed 1m 41s\n",
      "train Loss: 1.0872 Acc: 0.6954\n",
      "Time elapsed 1m 44s\n",
      "valid Loss: 1.8496 Acc: 0.5367\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 3/9\n",
      "----------\n",
      "Time elapsed 2m 15s\n",
      "train Loss: 0.9471 Acc: 0.7324\n",
      "Time elapsed 2m 19s\n",
      "valid Loss: 2.3721 Acc: 0.5122\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 4/9\n",
      "----------\n",
      "Time elapsed 2m 50s\n",
      "train Loss: 0.8842 Acc: 0.7505\n",
      "Time elapsed 2m 53s\n",
      "valid Loss: 2.4097 Acc: 0.4878\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 5/9\n",
      "----------\n",
      "Time elapsed 3m 25s\n",
      "train Loss: 0.8640 Acc: 0.7570\n",
      "Time elapsed 3m 28s\n",
      "valid Loss: 2.1172 Acc: 0.5281\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 6/9\n",
      "----------\n",
      "Time elapsed 3m 60s\n",
      "train Loss: 0.7180 Acc: 0.7915\n",
      "Time elapsed 4m 3s\n",
      "valid Loss: 2.0928 Acc: 0.5440\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 7/9\n",
      "----------\n",
      "Time elapsed 4m 34s\n",
      "train Loss: 0.7278 Acc: 0.7904\n",
      "Time elapsed 4m 38s\n",
      "valid Loss: 1.8000 Acc: 0.5746\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 8/9\n",
      "----------\n",
      "Time elapsed 5m 8s\n",
      "train Loss: 0.6223 Acc: 0.8152\n",
      "Time elapsed 5m 12s\n",
      "valid Loss: 1.8604 Acc: 0.5954\n",
      "Optimizer learning rate : 0.0010000\n",
      "Epoch 9/9\n",
      "----------\n",
      "Time elapsed 5m 43s\n",
      "train Loss: 0.5475 Acc: 0.8408\n",
      "Time elapsed 5m 46s\n",
      "valid Loss: 2.0773 Acc: 0.5611\n",
      "Optimizer learning rate : 0.0010000\n",
      "Training complete in 5m 46s\n",
      "Best val Acc: 0.595355\n"
     ]
    }
   ],
   "source": [
    "# 进行训练\n",
    "model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs  = train_model(model_ft, dataloaders, criterion, optimizer, num_epochs=10,)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "EbetBY4bd9_U"
   },
   "source": [
    "### 加载训练好的模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 68
    },
    "colab_type": "code",
    "id": "0IXiW9_2d9_U",
    "outputId": "2f21f369-df6a-433f-812a-1bbb87d30fa6",
    "ExecuteTime": {
     "end_time": "2024-04-10T16:05:09.963144600Z",
     "start_time": "2024-04-10T16:05:09.699296800Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<All keys matched successfully>"
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 初始化模型\n",
    "model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)\n",
    "\n",
    "# 移动到GPU或者CPU进行训练\n",
    "model_ft = model_ft.to(device)\n",
    "\n",
    "# 保存的模型的名字\n",
    "filename='best.pt'\n",
    "\n",
    "# 加载模型\n",
    "checkpoint = torch.load(filename)\n",
    "best_acc = checkpoint['best_acc']\n",
    "model_ft.load_state_dict(checkpoint['state_dict'])  # 使用之前的所有权重参数替换当前模型的权重参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "PXCZPK70d9_W"
   },
   "source": [
    "### 测试数据预处理\n",
    "\n",
    "- 测试数据处理方法需要跟训练时一直才可以 \n",
    "- crop操作的目的是保证输入的大小是一致的\n",
    "- 标准化操作也是必须的，用跟训练数据相同的mean和std,但是需要注意一点训练数据是在0-1上进行标准化，所以测试数据也需要先归一化\n",
    "- 最后一点，PyTorch中颜色通道是第一个维度，跟很多工具包都不一样，需要转换\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2024-04-10T16:10:01.186967400Z",
     "start_time": "2024-04-10T16:10:00.738263300Z"
    }
   },
   "outputs": [],
   "source": [
    "# 得到一个batch的测试数据（姑且使用验证集冒充测试集了）\n",
    "dataiter = iter(dataloaders['valid'])\n",
    "images, labels = next(dataiter)\n",
    "\n",
    "model_ft.eval()\n",
    "\n",
    "if train_on_gpu:\n",
    "    output = model_ft(images.cuda())  # 前向传播得到输出结果\n",
    "else:\n",
    "    output = model_ft(images)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "output表示对一个batch中每一个数据得到其属于各个类别的可能性"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-10T16:10:41.220506Z",
     "start_time": "2024-04-10T16:10:41.214587500Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "torch.Size([128, 102])"
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "output.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "取128个数据，每个数据分贝属于102类别的概率\n",
    "### 得到概率最大的那个"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-10T16:13:29.889414900Z",
     "start_time": "2024-04-10T16:13:29.874945Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "array([ 84,  53,  26,   8,  42, 100,  23,  90,  85,  64,  43,  96,  10,\n        62,  61,  24,  55,  60,  33,  56,  76,  61,  22,  55,  49,  84,\n        43,  88,  18,  55,  50,  64,  57,  27,  90,  85,  43,  97,  80,\n        30,  19,  95,  74,  85,  77,  49,  85,  40,  30,  96,  28,  28,\n        98,  24,  11,  74,  56,  77,  33,  10,  40,  75,  77,  72,  56,\n        59,  18,  75,  66,  49,  73,  59,  74,  83,  51,  22,  56,  74,\n        33,  50,  73,  70,  38, 100,  90,   9,  49,  49,  76,  61,  75,\n        49,  75,  55,  65,  82,  54, 101,  64,  11,  35,  49,  33,  34,\n        55,  59,  73,   3,  46,  12,  84,  84,  96,  97,  72,  43,  54,\n        89,  81,   6,  18,  65,  11,  49,  89,  49,  12, 101], dtype=int64)"
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 获取最大值的索引\n",
    "_, preds_tensor = torch.max(output, 1)\n",
    "\n",
    "# 获取预测结果\n",
    "preds = np.squeeze(preds_tensor.numpy()) if not train_on_gpu else np.squeeze(preds_tensor.cpu().numpy())  # 如果是GPU，要先转到CPU上\n",
    "preds"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 展示预测结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2024-04-10T16:32:47.814450200Z",
     "start_time": "2024-04-10T16:32:47.807676300Z"
    }
   },
   "outputs": [],
   "source": [
    "def im_convert(tensor):\n",
    "    \"\"\"将张量转换为图像\"\"\"\n",
    "    \n",
    "    image = tensor.to(\"cpu\").clone().detach()  # 将张量转移到 CPU 并进行深拷贝\n",
    "    image = image.numpy().squeeze()  # 将图像数据转换为 numpy 数组并去除单维度\n",
    "    image = image.transpose(1,2,0)  # 调整通道顺序为 (height, width, channels)\n",
    "    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))  # 对数据进行还原（前面对数据进行了归一化，所以这里需要还原）\n",
    "    image = image.clip(0, 1)  # 将像素值限制在 0 到 1 之间\n",
    "\n",
    "    return image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-10T16:32:49.748600200Z",
     "start_time": "2024-04-10T16:32:49.704197600Z"
    }
   },
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'images' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[1;31mNameError\u001B[0m                                 Traceback (most recent call last)",
      "Cell \u001B[1;32mIn[5], line 14\u001B[0m\n\u001B[0;32m     12\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m idx \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mrange\u001B[39m (columns\u001B[38;5;241m*\u001B[39mrows):\n\u001B[0;32m     13\u001B[0m     ax \u001B[38;5;241m=\u001B[39m fig\u001B[38;5;241m.\u001B[39madd_subplot(rows, columns, idx\u001B[38;5;241m+\u001B[39m\u001B[38;5;241m1\u001B[39m, xticks\u001B[38;5;241m=\u001B[39m[], yticks\u001B[38;5;241m=\u001B[39m[])\n\u001B[1;32m---> 14\u001B[0m     plt\u001B[38;5;241m.\u001B[39mimshow(im_convert(images[idx]))\n\u001B[0;32m     15\u001B[0m     \u001B[38;5;66;03m# 设置子图的标题，显示预测的类别和真实类别，并根据预测结果是否正确设置标题颜色为绿色或红色。\u001B[39;00m\n\u001B[0;32m     16\u001B[0m     ax\u001B[38;5;241m.\u001B[39mset_title(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m (\u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m)\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;241m.\u001B[39mformat(cat_to_name[\u001B[38;5;28mstr\u001B[39m(preds[idx])], cat_to_name[\u001B[38;5;28mstr\u001B[39m(labels[idx]\u001B[38;5;241m.\u001B[39mitem())]),\n\u001B[0;32m     17\u001B[0m                  color\u001B[38;5;241m=\u001B[39m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mgreen\u001B[39m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m cat_to_name[\u001B[38;5;28mstr\u001B[39m(preds[idx])]\u001B[38;5;241m==\u001B[39mcat_to_name[\u001B[38;5;28mstr\u001B[39m(labels[idx]\u001B[38;5;241m.\u001B[39mitem())] \u001B[38;5;28;01melse\u001B[39;00m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mred\u001B[39m\u001B[38;5;124m\"\u001B[39m))\n",
      "\u001B[1;31mNameError\u001B[0m: name 'images' is not defined"
     ]
    },
    {
     "data": {
      "text/plain": "<Figure size 2000x2000 with 1 Axes>",
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWQAAALQCAYAAACnqAreAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAKqklEQVR4nO3ZsWrjQBRA0ZFJK6c30f9/WEAfYPWeLZakMzG7Cbkk57QziFddxJtlzjkHAN/u9N0DAPCXIANECDJAhCADRAgyQIQgA0QIMkCEIANEPD1y6Xa7jX3fx7quY1mWr54J4EeZc47jOMblchmn0/3/4IeCvO/72Lbt04YD+I1eX1/Hy8vL3fOHgryu6/vHzufz50wG8Etcr9exbdt7S+95KMhva4rz+SzIAP/oo5WvRz2ACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIEGSACEEGiBBkgAhBBogQZIAIQQaIeHrk0pxzjDHG9Xr90mEAfqK3dr619J6HgnwcxxhjjG3b/nMsgN/rOI7x/Px893yZHyV7jHG73ca+72Nd17Esy6cOCPDTzTnHcRzjcrmM0+n+pvihIAPw9TzqAUQIMkCEIANECDJAhCADRAgyQIQgA0T8AUDpSCivOFIwAAAAAElFTkSuQmCC"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import os\n",
    "os.environ['PYDEVD_DISABLE_FILE_VALIDATION'] = '1'\n",
    "\n",
    "# 画图\n",
    "fig=plt.figure(figsize=(20, 20))  # 创建画布\n",
    "# 两行四列\n",
    "columns =4\n",
    "rows = 2\n",
    "\n",
    "for idx in range (columns*rows):\n",
    "    ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])\n",
    "    plt.imshow(im_convert(images[idx]))\n",
    "    # 设置子图的标题，显示预测的类别和真实类别，并根据预测结果是否正确设置标题颜色为绿色或红色。\n",
    "    ax.set_title(\"{} ({})\".format(cat_to_name[str(preds[idx])], cat_to_name[str(labels[idx].item())]),\n",
    "                 color=(\"green\" if cat_to_name[str(preds[idx])]==cat_to_name[str(labels[idx].item())] else \"red\"))\n",
    "plt.show()  # 展示绘制的图像。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "name": "Flowers Classification.ipynb",
   "provenance": [],
   "version": "0.3.2"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
